{-#LANGUAGE ScopedTypeVariables,FlexibleInstances,MultiParamTypeClasses,OverloadedStrings #-}
{-#LANGUAGE RecordWildCards,DataKinds#-}
module App.LibTemplate.LibTemplate
  ( testGinger
  , Handle(..)
  , renderTemplate
  , createTemplate
  , loadTemplateGroup
  , loadOneTemplate
  , TemplateConfig(..)
  )
where
import qualified Text.Ginger                   as TG
import qualified System.IO                     as SI
import           Control.Exception
import qualified Data.Text                     as DT
import           Data.Functor.Identity
import           App.Common.Table
import qualified Data.Aeson                    as DA
import qualified Data.Map                      as DM
import qualified Data.List                     as DL
import           App.LibTemplate.Impl
import qualified Data.ByteString.Internal      as BS
import           Data.String.Conversions
import           Data.Default
import           Control.Concurrent.STM
import           System.Directory
import qualified System.FilePath as SF
import Control.Monad
import Data.Either
import Control.Monad.Writer
import Data.Aeson

type TemplateGroup = DM.Map String (TG.Template TG.SourcePos)

data TemplateConfig = TemplateConfig {
   rootPath::String
  ,isDebug::Bool
  ,useSiteName::Maybe String
}

instance Default TemplateConfig where
  def = TemplateConfig {rootPath = "",isDebug = False,useSiteName = Nothing}

data Handle = Handle {
   config::TemplateConfig
  ,tplSites::TVar (DM.Map String TemplateGroup)
}

createTemplate :: TemplateConfig -> IO Handle
createTemplate cfg@TemplateConfig{..} = do
  when isDebug $ putStrLn "createTemplate"
  emptyMap <- newTVarIO DM.empty
  let handle = Handle {config = cfg,tplSites = emptyMap}
  case useSiteName of
    Nothing -> return handle
    Just sName -> do
      loadTemplateGroup handle (rootPath <> sName)
      return handle

loadOneTemplate::Handle -> String -> String -> IO (Maybe String)
loadOneTemplate h@Handle{..} groupName tplName = do
   eTpl <- parseTemplate $ rootPath config SF.</> groupName SF.</> tplName
   case eTpl of
    Left  l -> print l >> pure Nothing
    Right r -> do
      atomically $ modifyTVar tplSites (DM.adjust (DM.adjust (const r) tplName) groupName)
      return Nothing

loadTemplateGroup :: Handle -> String -> IO ()
loadTemplateGroup tplHandle@Handle{..} folderPath = do
  when (isDebug config)  (putStrLn "loadTemplateGroup")
  files <- listDirectory folderPath
  onlyFiles <- filterM doesFileExist (map (\x->folderPath <>"/" <> x) files)
  let fileNames = map SF.takeFileName onlyFiles
  tpls <- mapM pTemplate (zip onlyFiles fileNames)

  let rightTpl = mRights tpls
  let mapTG::TemplateGroup = DM.fromList rightTpl
  atomically $ modifyTVar tplSites (DM.insert (SF.takeBaseName folderPath) mapTG)
  --mm <- readTVarIO tplSites
  --print mm
  --return ()
 where
  pTemplate (path,name) = do
   tpl <- parseTemplate path
   if isLeft tpl then print (fromLeft "" tpl) else pure ()
   return (tpl,name)
  mRights::[(Either String (TG.Template TG.SourcePos), FilePath)] -> [(String,TG.Template TG.SourcePos)]
  mRights lst = map (\(a,b)-> (b,fromRight undefined a)) (filter (\(a,b) -> isRight a) lst)

destoryTemplateGroup :: Handle -> IO ()
destoryTemplateGroup handle = pure ()

renderTemplate::Handle -> String -> Value -> IO DT.Text
renderTemplate h@Handle{..} tplName val =
  case useSiteName config of
    Nothing    -> putStrLn "没有模板" >> pure ""
    Just gName -> render gName
 where
  render::String -> IO DT.Text
  render name = do
    when (isDebug config) (loadOneTemplate h name tplName >> putStrLn  ("reload template " <> tplName))
    sites <- readTVarIO tplSites
    let tpl = sites  DM.! name DM.! tplName
    return $ TG.easyRender val tpl


loadFile :: FilePath -> IO String
loadFile path = SI.openFile path SI.ReadMode >>= SI.hGetContents

loadFileMay :: FilePath -> IO (Maybe String)
loadFileMay path = do
  eVal :: (Either IOException String) <- try (loadFile path)
  case eVal of
    Right r -> pure $ Just r
    Left  e -> print e >> pure Nothing

parseTemplate :: String -> IO (Either String (TG.Template TG.SourcePos))
parseTemplate path = do
  maySource <- loadFileMay path
  case maySource of
    Nothing -> pure $ Left "加载文件失败"
    Just js -> parse js
 where
  parse :: String -> IO (Either String (TG.Template TG.SourcePos))
  parse str = do
    res <- TG.parseGinger loadFileMay (Just path) str
    case res of
      Left  le -> pure $ Left (show le)
      Right r  -> pure $ Right r


testGinger :: IO ()
testGinger = do
  putStrLn "testGinger"
  templateHandle <- createTemplate $ TemplateConfig
        { rootPath    = "static/template/"
        , isDebug     = False
        , useSiteName = Just "default"
        }
  let str::Value = toJSON (DM.fromList [("f","AAA")]::DM.Map String String)
  txt <- renderTemplate templateHandle "index.html"  str
  putStrLn "=================="
  print txt
  return ()
